Creating a nested tag structure (e.g., Coding/JavaScript) is definitely possible and adds a sophisticated layer to your blog's organization.
To achieve this, we will use a forward-slash convention in your Markdown front matter (like tags: ["Coding/JavaScript"]) and create a new filter to parse those into a tree.
1. Update your .eleventy.js
We need a new filter called groupTagsByPath. This will take your flat list of tags and turn them into a nested object. Add this inside your module.exports:
JavaScript
eleventyConfig.addFilter("groupTagsByPath", function(allTags, collections) {
const tree = {};
Object.keys(allTags).forEach(tag => {
// Skip the built-in 'all' and 'post' tags
if (tag === 'all' || tag === 'post') return;
const parts = tag.split('/');
let current = tree;
parts.forEach((part, index) => {
if (!current[part]) {
current[part] = { _posts: [], _children: {} };
}
// If it's the leaf node (the end of the path), add the posts
if (index === parts.length - 1) {
current[part]._posts = collections[tag] || [];
}
current = current[part]._children;
});
});
return tree;
});
### ---
2. Update the Sidebar in layout.njk
Replace the "Recent Posts" section we previously discussed with this new Tag Tree. We use a recursive macro (a Nunjucks function) to handle the levels, which allows for infinite nesting (Level1 > Level2 > Level3, etc.).
HTML
<aside>
<section class="sidebar-section">
<h3>Search</h3>
<input type="text" id="search-input" placeholder="Search...">
<ul id="search-results" style="display: none;"></ul>
</section>
<hr>
<section class="sidebar-section">
<h3>Tags</h3>
<div class="archive-tree">
{% macro renderTag(name, data) %}
<details>
<summary>{{ name }} {% if data._posts.length > 0 %}({{ data._posts.length }}){% endif %}</summary>
<div style="margin-left: 15px;">
{# Render sub-tags if they exist #}
{% for childName, childData in data._children %}
{{ renderTag(childName, childData) }}
{% endfor %}
{# Render posts at this level #}
<ul class="post-list">
{% for post in data._posts %}
<li><a href="{{ post.url }}">{{ post.data.title }}</a></li>
{% endfor %}
</ul>
</div>
</details>
{% endmacro %}
{% set tagTree = collections | groupTagsByPath(collections) %}
{% for tagName, tagData in tagTree %}
{{ renderTag(tagName, tagData) }}
{% endfor %}
</div>
</section>
<hr>
</aside>
### ---
3. How to use it in your Posts
When you write a blog post, simply use the slash in your tags:
YAML
---
title: Learning React
date: 2026-02-05
tags:
- "Coding/Frontend/React"
- "Personal/Journal"
---
How this works
- Parsing: The filter splits the tag string by /. It builds a nested JavaScript object where each level can have both its own posts and "child" tags.
- Recursion: The {% macro %} in Nunjucks is like a function that calls itself. If it finds a sub-category, it runs again to create another dropdown.
- UI: Because we used the same .archive-tree class, the CSS we wrote earlier (with the ▶ and ▼ arrows) will automatically apply to this tag tree as well!
4. Push to Cloudflare
Bash
git add .
git commit -m "Add hierarchical tag tree"
git push origin main
Since you now have a complex tag system, would you like me to show you how to display "Related Posts" at the bottom of a post based on shared tags?